import type { RedisClient } from "./client";

export class ListCommand {
    constructor(client: RedisClient) {
        this.client = client;
    }

    private readonly client: RedisClient;

    // return the element being popped from source and pushed to destination.
    async blmove(srcKey: string, destKey: string, timeout: number, srcOpt: "LEFT" | "RIGHT", destOpt: "" | "LEFT" | "RIGHT"): Promise<string | null> {
        const args = ["BLMOVE", srcKey, destKey];
        args.push(srcOpt, destOpt);
        args.push(timeout.toFixed(0));
        const res = await this.client.exec(...args);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    async blmpop(timeout: number, numkeys: number, keyList: string[], opt: "LEFT" | "RIGHT", count: number = 0): Promise<unknown> {
        const args = ["BLMPOP", timeout.toFixed(), numkeys.toFixed(0), ...keyList];
        args.push(opt);

        if (count > 0) {
            args.push("COUNT", count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async blpop(keyList: string[], timeout: number): Promise<unknown> {
        const res = await this.client.exec("BLPOP", ...keyList, timeout.toFixed(0));
        return res;
    }

    async brpop(keyList: string[], timeout: number): Promise<unknown> {
        const res = await this.client.exec("BRPOP", ...keyList, timeout.toFixed(0));
        return res;
    }

    // return the requested element, or nil when index is out of range.
    async lindex(key: string, index: number): Promise<string | null> {
        const res = await this.client.exec("LINDEX", key, index.toFixed(0));
        if (res == null) {
            return null;
        }
        return res as string;
    }

    // return the list length after a successful insert operation, 0 if the key doesn't exist, and -1 when the pivot wasn't found.
    async linsert(key: string, opt: "BEFORE" | "AFTER", pivot: string, value: string): Promise<number> {
        const res = await this.client.exec("LINSERT", key, opt, pivot, value);
        return res as number;
    }

    // return the length of the list at key.
    async llen(key: string): Promise<number> {
        const res = await this.client.exec("LLEN", key);
        return res as number;
    }

    async lmove(srcKey: string, destKey: string, srcOpt: "LEFT" | "RIGHT", destOpt: "LEFT" | "RIGHT"): Promise<string | null> {
        const res = await this.client.exec("LMOVE", srcKey, destKey, srcOpt, destOpt);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    async lmpop(numkeys: number, keyList: string[], opt: "LEFT" | "RIGHT", count: number = 0): Promise<unknown> {
        const args = ["LMPOP", numkeys.toFixed(0), ...keyList, opt];
        if (count > 0) {
            args.push(count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async lpop(key: string, count: number = 0): Promise<unknown> {
        const args = ["LPOP", key];
        if (count > 0) {
            args.push(count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    async lpos(key: string, value: string, rank: number = 0, count: number = 0, maxlen: number = 0): Promise<unknown> {
        const args = ["LPOS", key, value];
        if (rank > 0) {
            args.push("RANK", rank.toFixed(0));
        }
        if (count > 0) {
            args.push("COUNT", count.toFixed(0));
        }
        if (maxlen > 0) {
            args.push("MAXLEN", maxlen.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    // return the length of the list after the push operations.
    async lpush(key: string, valueList: string[]): Promise<number> {
        const res = await this.client.exec("LPUSH", key, ...valueList);
        return res as number;
    }

    // return the length of the list after the push operation.
    async lpushx(key: string, valueList: string[]): Promise<number> {
        const res = await this.client.exec("LPUSHX", key, ...valueList);
        return res as number;
    }

    // return list of elements in the specified range.
    async lrange(key: string, start: number, stop: number): Promise<string[]> {
        const res = await this.client.exec("LRANGE", key, start.toFixed(0), stop.toFixed(0));
        return res as string[];
    }

    // return the number of removed elements.
    async lrem(key: string, count: number, value: string): Promise<number> {
        const res = await this.client.exec("LREM", key, count.toFixed(0), value);
        return res as number;
    }

    async lset(key: string, index: number, value: string): Promise<string> {
        const res = await this.client.exec("LSET", key, index.toFixed(0), value);
        return res as string;
    }

    async ltrim(key: string, start: number, stop: number): Promise<string> {
        const res = await this.client.exec("LTRIM", key, start.toFixed(0), stop.toFixed(0));
        return res as string;
    }

    async rpop(key: string, count: number = 0): Promise<unknown> {
        const args = ["RPOP", key];
        if (count > 0) {
            args.push(count.toFixed(0));
        }
        const res = await this.client.exec(...args);
        return res;
    }

    // return the length of the list after the push operation.
    async rpush(key: string, valueList: string[]): Promise<number> {
        const res = await this.client.exec("RPUSH", key, ...valueList);
        return res as number;
    }

    // return the length of the list after the push operation.
    async rpushx(key: string, valueList: string[]): Promise<number> {
        const res = await this.client.exec("RPUSHX", key, ...valueList);
        return res as number;
    }
}